package ch.ethz.fcl.metrobuzz.render.techniques.bezier;

import javax.vecmath.Vector2d;

import org.lwjgl.opengl.GL11;

import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;
import ch.ethz.fcl.metrobuzz.tools.MBTools;

public class Bezier {
	public static void drawBezierCurve(Point2D... ctrlPoints) {
		Point2D[] points = getBezierSpline(100, ctrlPoints);

		// Draw curve.
		GL11.glBegin(GL11.GL_LINE_STRIP);

		for (int i = 0; i < points.length; i++) {
			GL11.glVertex3f(points[i].getX(), points[i].getY(), 0);
		}

		GL11.glEnd();
	}

	/*
	 * returns a smooth spline that pass through all the control points with
	 * certain degree of cubic
	 */
	public static Point2D[] getContinuousBezierSpline(double cubicDegree,
			Point2D... points) {
		int segmentSize = 100;
		Point2D[] spline = new Point2D[(points.length - 1) * segmentSize + 1];

		for (int i = 0; i < points.length - 1; i++) {
			int rotateDir = ((i % 2) == 0) ? 1 : -1;
			Point2D p0 = points[i];
			Point2D p3 = points[i + 1];
			float length = p0.distance(p3) * 0.5f;

			Vector2d dir = new Vector2d(p3.getX() - p0.getX(), p3.getY()
					- p0.getY());
			Vector2d dir01 = MBTools.rotate(dir, cubicDegree * rotateDir);
			Vector2d dir32 = MBTools.rotate(dir, Math.PI - cubicDegree
					* rotateDir);

			Point2D p1 = p0.getPointAt(dir01, length);
			Point2D p2 = p3.getPointAt(dir32, length);

			Point2D[] ctrlPoints = { p0, p1, p2, p3 };

			Point2D[] bezier = getBezierSpline(segmentSize, ctrlPoints);
			for (int j = 0; j < bezier.length - 1; j++)
				spline[i * segmentSize + j] = bezier[j];
		}

		spline[spline.length - 1] = points[points.length - 1];
		return spline;
	}

	public static Point2D[] getBezierSpline(int size, Point2D... ctrlPoints) {
		if (ctrlPoints.length == 2)
			return linearBezier(ctrlPoints, size);

		else if (ctrlPoints.length == 3)
			return quadraticBezier(ctrlPoints, size);

		else if (ctrlPoints.length == 4)
			return cubicBezier(ctrlPoints, size);

		else
			return null;
	}

	private static Point2D[] linearBezier(Point2D[] ctrlPoints, int size) {
		Point2D[] points = new Point2D[size + 1];
		for (int i = 0; i <= size; i++) {
			float t = (float) i / size;
			float x = (1 - t) * ctrlPoints[0].getX() + t * ctrlPoints[1].getX();
			float y = (1 - t) * ctrlPoints[0].getY() + t * ctrlPoints[1].getY();

			points[i] = new Point2D(x, y);
		}

		return points;
	}

	private static Point2D[] quadraticBezier(Point2D[] ctrlPoints, int size) {
		Point2D[] points = new Point2D[size + 1];
		for (int i = 0; i <= size; i++) {
			float t = (float) i / size;
			float x = (1 - t) * (1 - t) * ctrlPoints[0].getX() + 2 * (1 - t)
					* t * ctrlPoints[1].getX() + t * t * ctrlPoints[2].getX();
			float y = (1 - t) * (1 - t) * ctrlPoints[0].getY() + 2 * (1 - t)
					* t * ctrlPoints[1].getY() + t * t * ctrlPoints[2].getY();

			points[i] = new Point2D(x, y);
		}

		return points;
	}

	private static Point2D[] cubicBezier(Point2D[] ctrlPoints, int size) {
		Point2D[] points = new Point2D[size + 1];
		for (int i = 0; i <= size; i++) {
			float t = (float) i / size;
			float x = (1 - t) * (1 - t) * (1 - t) * ctrlPoints[0].getX() + 3
					* (1 - t) * (1 - t) * t * ctrlPoints[1].getX() + 3
					* (1 - t) * t * t * ctrlPoints[2].getX() + t * t * t
					* ctrlPoints[3].getX();
			float y = (1 - t) * (1 - t) * (1 - t) * ctrlPoints[0].getY() + 3
					* (1 - t) * (1 - t) * t * ctrlPoints[1].getY() + 3
					* (1 - t) * t * t * ctrlPoints[2].getY() + t * t * t
					* ctrlPoints[3].getY();
			points[i] = new Point2D(x, y);
		}

		return points;
	}
}
